package dryven.model.binding.form;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.omg.CORBA._PolicyStub;

import dryven.annotations.FormModel;
import dryven.config.DatabaseConfigurationSource;
import dryven.model.binding.fieldset.Field;
import dryven.model.binding.fieldset.FieldGroup;
import dryven.model.binding.fieldset.FieldSet;
import dryven.model.binding.fieldset.FieldValidatorDescription;
import dryven.model.di.DependencyService;
import dryven.model.validation.HttpValidationFormValueCollection;
import dryven.model.validation.ValidationException;
import dryven.model.validation.ValidationFormValueCollection;
import dryven.model.validation.ValidatorService;
import dryven.model.validation.ValidatorSession;
import dryven.persistence.provider.PersistenceProvider;
import dryven.request.controller.ControllerDescription;
import dryven.request.controller.RedirectException;
import dryven.request.controller.paramtransform.ActionParameterBindCandidate;
import dryven.request.controller.paramtransform.ActionParameterTransformFeedback;
import dryven.request.controller.paramtransform.ActionParameterTransformResult;
import dryven.request.controller.paramtransform.ActionParameterTransformationException;
import dryven.request.controller.paramtransform.ActionParameterTransformer;
import dryven.request.http.Request;
import dryven.request.http.Response;
import dryven.request.routing.RoutingService;
import dryven.unittest.mocks.MockRequest;
import dryven.util.TypeUtil;
import dryven.util.crypt.AESGeneratedKey;

public class FormDecoder implements ActionParameterBindCandidate {

	private ValidatorService _validatorService;
	private PersistenceProvider _persistenceProvider;
	private DatabaseConfigurationSource _dbConfigSource;
	private AESGeneratedKey _aesKey;
	private String _signSecret;
	private RoutingService _routingService;
	
	public FormDecoder(ValidatorService validatorService,
			PersistenceProvider persistenceProvider,
			DatabaseConfigurationSource dbConfigSource,
			AESGeneratedKey aesKey,
			String signSecret,
			RoutingService routingService) {
		super();
		_validatorService = validatorService;
		_persistenceProvider = persistenceProvider;
		_dbConfigSource = dbConfigSource;
		_aesKey = aesKey;
		_signSecret = signSecret;
		_routingService = routingService;
	}

	@Override
	public void apply(Class<?> type, Annotation[] annotations,
			Object currentValue, Request request, Response response,
			ActionParameterTransformFeedback feedback) {

		String cacheName = FormDecoder.class.getName() + "models";
		/* get the already converted models from the feedback cache
		 * so we don't have to validate the models for every model that
		 * has to be injected in the controller method
		 */
		DecodedFormModelCollection models = feedback.getCacheValue(cacheName,
				DecodedFormModelCollection.class);
		if (models == null) {
			FormSignatureDecoder decoder = new FormSignatureDecoder(
					request.getBodyParam(Form.SignatureField),
					_aesKey,
					_signSecret);
			ValidatorSession validator = _validatorService.newSession(request);
			models = decodeModels(validator, request, decoder);
			if(validator.hasErrors()) {
				ControllerDescription errorAction = prepareErrorAction(
						models, decoder, validator, request, response);
				feedback.redirect(errorAction);
				return;
			}
			feedback.addCacheValue(cacheName, models);
		}
		Object model = models.getModel(type);
		if(model==null) {
			throw new RuntimeException(String.format("No model found with type %s in the sent form. Are you sure it a field from this type was included in the field set?", type.getName()));
		}
		feedback.setParameter(model);

	}

	private ControllerDescription prepareErrorAction(
			DecodedFormModelCollection models, FormSignatureDecoder decoder,
			ValidatorSession validator, Request request, Response response) {
		
		String errorURL = decoder.getErrorActionURL();
		ControllerDescription errorAction = _routingService.routeToController(new MockRequest(errorURL), response);
		
		//prevent endless loop
		Method errorActionMethod = errorAction.getAction();
		for (Annotation[] annos : errorActionMethod.getParameterAnnotations()) {
			for (Annotation a : annos) {
				if(a.equals(FormModel.class)) {
					throw new RuntimeException(String.format("Cannot redirect to form origin action (%s)"+
							" because it too has a FormModel annotation. Only form destiny actions should"+
							" have FormModel annotation. The action was canceled because it would very"+
							" likely result in an endless loop.",errorActionMethod.toString()));
				}
			}
		}
		int index = 0;
		FormValidation formValidation = new FormValidation(models, validator, decoder.getFieldsetBinding());
		for (Class<?> paramType : errorAction.getAction().getParameterTypes()) {
			if(paramType.equals(FormValidation.class)) {
				errorAction.setActionParameter(index, formValidation);
			}
			++index;
		}
		return errorAction;
	}

	public DecodedFormModelCollection decodeModels(ValidatorSession validator, Request request,
			FormSignatureDecoder decoder) {

		FieldSet fs = decoder.getFieldsetBinding().invoke();
		// this is a wrapper around the request POST values
		ValidationFormValueCollection formValues = new HttpValidationFormValueCollection(
				request);
		DecodedFormModelCollection models = new DecodedFormModelCollection(
				_dbConfigSource);

		for (FieldGroup group : fs.groups()) {
			for (Field field : group.getFields()) {
				Class<?> modelType = field.getModelType();
				Object model = models.getOrCreateModel(modelType, decoder
						.getPrimaryKeyForModel(modelType), request);
				FieldValidatorDescription desc = new FieldValidatorDescription(
						field);
				try {
					Object validatedValue = validator.validate(desc, formValues,
						field.getValidatorClass());
					if (TypeUtil.typesAreAssignable(field.getValueType(), validatedValue.getClass())) {
						field.setValue(model, validatedValue);
					} else {
						throw new RuntimeException(
								"validator created value of wrong type");
					}
				} catch(ValidationException ve) {
					//keep validating so we get as much error messages as possible
					continue;
				}
			}
		}

		return models;
	}

	@Override
	public boolean canApply(Class<?> type, Annotation[] annotations) {
		if (type.isPrimitive()) {
			return false;
		}
		for (Annotation annotation : annotations) {
			if (annotation.annotationType().equals(FormModel.class)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isValueGenerator() {
		return true;
	}

	@Override
	public String serializeParameter(Object o) {
		return null;
	}

}

class TransformResult implements ActionParameterTransformResult {

	private Object[] _parameters;
	
	@Override
	public void setOptionalFilledParameterCount(int count) {}

	@Override
	public void setParameters(Object[] o) {
		_parameters = o;
	}

	public Object[] getParameters() {
		return _parameters;
	}

	@Override
	public void setTotalOptionalParameterCount(int count) {}
	
}
